<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 6.3.0">
  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon.jpg">
  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon.jpg">
  <link rel="mask-icon" href="/images/logo.svg" color="#222">

<link rel="stylesheet" href="/css/main.css">


<link rel="stylesheet" href="/lib/font-awesome/css/all.min.css">

<script id="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"matian.connorma.cn","root":"/","scheme":"Muse","version":"7.8.0","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":false,"show_result":false,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":false,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}}};
  </script>

  <meta name="description" content="昨天看了下 Spring Security 的使用，打着断点弄清了认证的流程。 首先，可以了解下涉及到的关键的几个类，形成一个整体的了解。 主要的类 Authentication  Authentication接口，子类有 AbstractAuthenticationToken，直接用的实现常是 UsernamePasswordAuthenticationToken 类，用于保存认证信息，最重要的">
<meta property="og:type" content="article">
<meta property="og:title" content="Spring security 登陆认证的重要概念、流程梳理及自定义设置">
<meta property="og:url" content="http://matian.connorma.cn/919d4f23.html">
<meta property="og:site_name" content="马天">
<meta property="og:description" content="昨天看了下 Spring Security 的使用，打着断点弄清了认证的流程。 首先，可以了解下涉及到的关键的几个类，形成一个整体的了解。 主要的类 Authentication  Authentication接口，子类有 AbstractAuthenticationToken，直接用的实现常是 UsernamePasswordAuthenticationToken 类，用于保存认证信息，最重要的">
<meta property="og:locale" content="zh_CN">
<meta property="article:published_time" content="2018-07-28T12:02:55.000Z">
<meta property="article:modified_time" content="2024-01-25T12:19:17.207Z">
<meta property="article:author" content="马天">
<meta property="article:tag" content="java">
<meta property="article:tag" content="Spring Boot">
<meta property="article:tag" content="Spring security">
<meta name="twitter:card" content="summary">

<link rel="canonical" href="http://matian.connorma.cn/919d4f23.html">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : false,
    isPost : true,
    lang   : 'zh-CN'
  };
</script>

  <title>Spring security 登陆认证的重要概念、流程梳理及自定义设置 | 马天</title>
  






  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container use-motion">
    <div class="headband"></div>

    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏">
      <span class="toggle-line toggle-line-first"></span>
      <span class="toggle-line toggle-line-middle"></span>
      <span class="toggle-line toggle-line-last"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/" class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <h1 class="site-title">马天</h1>
      <span class="logo-line-after"><i></i></span>
    </a>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
    </div>
  </div>
</div>




<nav class="site-nav">
  <ul id="menu" class="main-menu menu">
        <li class="menu-item menu-item-home">

    <a href="/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a>

  </li>
        <li class="menu-item menu-item-about">

    <a href="/about/" rel="section"><i class="fa fa-user fa-fw"></i>关于</a>

  </li>
        <li class="menu-item menu-item-tags">

    <a href="/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签</a>

  </li>
        <li class="menu-item menu-item-categories">

    <a href="/categories/" rel="section"><i class="fa fa-th fa-fw"></i>分类</a>

  </li>
        <li class="menu-item menu-item-archives">

    <a href="/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a>

  </li>
  </ul>
</nav>




</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>


    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          

          <div class="content post posts-expand">
            

    
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://matian.connorma.cn/919d4f23.html">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="马天">
      <meta itemprop="description" content="程序猿">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="马天">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          Spring security 登陆认证的重要概念、流程梳理及自定义设置
        </h1>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>

              <time title="创建时间：2018-07-28 20:02:55" itemprop="dateCreated datePublished" datetime="2018-07-28T20:02:55+08:00">2018-07-28</time>
            </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/Spring/" itemprop="url" rel="index"><span itemprop="name">Spring</span></a>
                </span>
            </span>

          

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
        <p>昨天看了下 Spring Security 的使用，打着断点弄清了认证的流程。</p>
<p>首先，可以了解下涉及到的关键的几个类，形成一个整体的了解。</p>
<h2 id="主要的类"><a href="#主要的类" class="headerlink" title="主要的类"></a>主要的类</h2><ol>
<li>Authentication</li>
</ol>
<p>Authentication接口，子类有 AbstractAuthenticationToken，直接用的实现常是 UsernamePasswordAuthenticationToken 类，用于保存认证信息，最重要的字段有：</p>
<ul>
<li>principal</li>
</ul>
<p>对应用户名之类的用户ID。</p>
<ul>
<li>credentials</li>
</ul>
<p>根据用户信息加密生成的凭证，用于认证。</p>
<span id="more"></span>


<ol start="2">
<li>UsernamePasswordAuthenticationFilter</li>
</ol>
<p>UsernamePasswordAuthenticationFilter 实现了AbstractAuthenticationProcessingFilter。filter 负责从请求中获取用户名和密码，生成 UsernamePasswordAuthenticationToken 交由 AuthenticationManager 进行验证。</p>
<ol start="3">
<li>UserDetails</li>
</ol>
<p>spring security 为我们提供了一个用于认证的 User 类，作为一个“Models core user information retrieved by a UserDetailsService”，继承自 UserDetails 接口，其中包含的字段有：</p>
<ul>
<li><p>String password</p>
</li>
<li><p>String username</p>
</li>
<li><p>Set authorities</p>
</li>
<li><p>boolean accountNonExpired</p>
</li>
<li><p>boolean accountNonLocked</p>
</li>
<li><p>boolean credentialsNonExpired</p>
</li>
<li><p>boolean enabled</p>
</li>
</ul>
<p> 这些字段中，除了 password 之外，全部是 final 的。关于这些字段的作用，我们在后文的流程梳理中进行说明。需要注意的是，参照 User 类的构造方法，特别指明了 authorities 参数不应为 null。authorities 用于控制用户的权限，我们熟悉的以下代码中的写法里为用户设定的 role，其实就是定制化的 authorities。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">auth.inMemoryAuthentication().withUser(<span class="string">&quot;xxx&quot;</span>).password(<span class="string">&quot;xxxxxx&quot;</span>).roles(<span class="string">&quot;USER&quot;</span>);</span><br></pre></td></tr></table></figure>

<p>对应的，spring security 提供了一个读取 UserDetails 的接口 UserDetailsService，这一接口中只有 loadUserByUsername(String username) 方法，作用是根据 username 取出服务端保存的UserDetails，以用于和客户端发送的信息组建成的 UserDetails 进行对比。</p>
<p>UserDetailsManager 接口继承了 UserDetailsService，其实现有 InMemoryUserDetailsManager 和 JdbcUserDetailsManager，分别实现了通过内存和 JDBC 存取用户认证信息。</p>
<ol>
<li>AuthenticationProvider</li>
</ol>
<p>提供认证服务，实现有 AbstractUserDetailsAuthenticationProvider 类，一方面，管理使用的 UserDetailsService 和 PasswordEncoder；另外，其中管理了多个 UserDetailsChecker 进行 UserDetails 的比对和检查判断其有效性，并为进行认证的方法 authenticate(Authentication authentication) 提供了默认实现。</p>
<p>如果不需要自定义 authenticate(Authentication authentication) 方法，不必对其进行修改，一般只需要设置使用的 UserDetailsService 和 PasswordEncoder。</p>
<ol start="2">
<li>PasswordEncoder</li>
</ol>
<p>负责对密码进行编码，包括 BCryptPasswordEncoder 及 MessageDigestPasswordEncoder 等多个实现。还有一个建议仅用于测试的 NoOpPasswordEncoder 的实现，顾名思义，这一实现在密码的存储中不进行加密编码。</p>
<ol start="3">
<li>SavedRequest 和 RequestCache</li>
</ol>
<p>可保存登陆请求。如果我们希望实现这一功能：未认证的用户访问某个需要认证的地址 A ——&gt; 跳转到登陆页面，要求用户登陆 ——&gt; 用户登陆成功后自动跳转回 A，那么我们就需要保存用户对 A 的 HttpServletRequest。spring security 在默认情况下使用 SavedRequestAwareAuthenticationSuccessHandler 来实现登陆成功后的跳转，其父类 SimpleUrlAuthenticationSuccessHandler 则实现了登陆成功后跳转到固定的设置的默认页面。</p>
<p>如果我们向在登陆成功后的处理中加入部分我们自定义的功能，那么从 SavedRequestAwareAuthenticationSuccessHandler 继承并不方便，万幸 spring security 使用 RequestCache 和 SavedRequest 对此功能进行了很充分的支持，我们可以直接从最原始的接口 AuthenticationSuccessHandler 来实现，加入登陆后跳转到原地址的功能，也可以继承 SimpleUrlAuthenticationSuccessHandler 来实现。只需要在我们的 handler 中初始化一个 RequestCache，就可以从其中读取 SavedRequest 进而获取我们需要的重定向地址。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// HttpSessionRequestCache 是 RequestCache 的实现，</span></span><br><span class="line"><span class="comment">// 在 HttpServletRequest 的 session 中使用 key 为 “SPRING_SECURITY_SAVED_REQUEST” 的字段保存请求信息。</span></span><br><span class="line"><span class="keyword">private</span> <span class="type">RequestCache</span> <span class="variable">mRequestCache</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HttpSessionRequestCache</span>();</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line">  <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onAuthenticationSuccess</span><span class="params">(HttpServletRequest request, HttpServletResponse response, Authentication authentication)</span> <span class="keyword">throws</span> IOException, ServletException &#123;</span><br><span class="line">     <span class="comment">// 可以直接使用 HttpSessionRequestCache 保存 HttpServletRequest 的 key 从 session 中读取，</span></span><br><span class="line">		<span class="comment">// 但这显然不是一个优雅安全的方式...</span></span><br><span class="line">	  <span class="comment">//SavedRequest savedRequest = (SavedRequest) request.getSession().getAttribute(&quot;SPRING_SECURITY_SAVED_REQUEST&quot;);</span></span><br><span class="line"></span><br><span class="line">      <span class="comment">// 使用 HttpSessionRequestCache 提供的方法读取</span></span><br><span class="line">		<span class="type">SavedRequest</span> <span class="variable">savedRequest</span> <span class="operator">=</span> mRequestCache.getRequest(request, response);</span><br><span class="line">      <span class="type">String</span> <span class="variable">s</span> <span class="operator">=</span> savedRequest.getRedirectUrl();</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>

<h2 id="认证流程"><a href="#认证流程" class="headerlink" title="认证流程"></a>认证流程</h2><ol>
<li><p>调用 filterChain 中的各个 filter。调用 UsernamePasswordAuthenticationFilter，获取客户端发送的用户名和密码等信息，组建出 AuthenticationToken。</p>
</li>
<li><p>将 AuthenticationToken 交由 AuthenticationManager 管理其认证。AuthenticationManager 调用 AuthenticationProvider 提供的认证服务。认证通过时，构建一个 Authentication 返回给用户；当认证不通过时，AuthenticationProvider 抛出相应异常，同样的构建认证失败的 Authentication 返回给用户。此处我们也可以自定义 handler 自定义返回给用户的信息。</p>
</li>
<li><p>以下进入 AbstractUserDetailsAuthenticationProvider 的 authenticate(Authentication authentication) 方法看下具体的认证过程</p>
</li>
</ol>
<p>首先，尝试从缓存中读取服务端保存的该用户的认证信息，未使用缓存或者缓存未命中时则调用 retrieveUser() 方法读取。AbstractUserDetailsAuthenticationProvider 提供的实现只有 DaoAuthenticationProvider，是通过 UserDetailsService 读取。无法读取时抛出异常 UsernameNotFoundException，认证不通过。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">   <span class="type">boolean</span> <span class="variable">cacheWasUsed</span> <span class="operator">=</span> <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">UserDetails</span> <span class="variable">user</span> <span class="operator">=</span> <span class="built_in">this</span>.userCache.getUserFromCache(username);</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (user == <span class="literal">null</span>) &#123;</span><br><span class="line"> 	cacheWasUsed = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">	<span class="keyword">try</span> &#123;</span><br><span class="line">		user = retrieveUser(username,</span><br><span class="line">					(UsernamePasswordAuthenticationToken) authentication);</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">catch</span> (UsernameNotFoundException notFound) &#123;</span><br><span class="line">		logger.debug(<span class="string">&quot;User &#x27;&quot;</span> + username + <span class="string">&quot;&#x27; not found&quot;</span>);</span><br><span class="line"></span><br><span class="line">		<span class="keyword">if</span> (hideUserNotFoundExceptions) &#123;</span><br><span class="line">			<span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">BadCredentialsException</span>(messages.getMessage(</span><br><span class="line">						<span class="string">&quot;AbstractUserDetailsAuthenticationProvider.badCredentials&quot;</span>,</span><br><span class="line">						<span class="string">&quot;Bad credentials&quot;</span>));</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">else</span> &#123;</span><br><span class="line">			<span class="keyword">throw</span> notFound;</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	Assert.notNull(user,</span><br><span class="line">				<span class="string">&quot;retrieveUser returned null - a violation of the interface contract&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>如果成功读到了服务端保存的用户信息，则依次调用了以下函数继续认证过程。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"> preAuthenticationChecks.check(user);</span><br><span class="line">additionalAuthenticationChecks(user,</span><br><span class="line">		(UsernamePasswordAuthenticationToken) authentication);</span><br></pre></td></tr></table></figure>

<p>AbstractUserDetailsAuthenticationProvider 使用 DefaultPreAuthenticationChecks 作为<br>preAuthenticationChecks，其 check 实现依次检查上述 UserDetails 中的 accountNonLocked、enabled 和 accountNonExpired 字段，检查了是否被锁定、不可用和过期。</p>
<p>接下来在 additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication) 方法中调用 passwordEncoder 进行 password 的验证。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"> <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">additionalAuthenticationChecks</span><span class="params">(UserDetails userDetails,</span></span><br><span class="line"><span class="params">	UsernamePasswordAuthenticationToken authentication)</span></span><br><span class="line">	<span class="keyword">throws</span> AuthenticationException &#123;</span><br><span class="line"><span class="keyword">if</span> (authentication.getCredentials() == <span class="literal">null</span>) &#123;</span><br><span class="line">	logger.debug(<span class="string">&quot;Authentication failed: no credentials provided&quot;</span>);</span><br><span class="line"></span><br><span class="line">	<span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">BadCredentialsException</span>(messages.getMessage(</span><br><span class="line">			<span class="string">&quot;AbstractUserDetailsAuthenticationProvider.badCredentials&quot;</span>,</span><br><span class="line">			<span class="string">&quot;Bad credentials&quot;</span>));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">String</span> <span class="variable">presentedPassword</span> <span class="operator">=</span> authentication.getCredentials().toString();</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) &#123;</span><br><span class="line">	logger.debug(<span class="string">&quot;Authentication failed: password does not match stored value&quot;</span>);</span><br><span class="line"></span><br><span class="line">	<span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">BadCredentialsException</span>(messages.getMessage(</span><br><span class="line">			<span class="string">&quot;AbstractUserDetailsAuthenticationProvider.badCredentials&quot;</span>,</span><br><span class="line">			<span class="string">&quot;Bad credentials&quot;</span>));</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>过程很简单，credentials 不为空时，使用设置的 passwordEncoder 对客户端发送的密码编码，与服务端存储的已编码的密码比对。同样的，比对不一致时抛出异常。</p>
<p>最后，使用 DefaultPostAuthenticationChecks 的实例 postAuthenticationChecks 检查 credentials 是否已经过期。</p>
<p>通过全部验证后调用 createSuccessAuthentication(principalToReturn, authentication, user) 构建一个 Authentication 返回给用户。</p>
<p>在 createSuccessAuthentication() 部分需要注意的是，默认情况下传入的参数 principalToReturn 和 user 都是从我们指定的 UserDetailsService 中的 loadUserByUsername(String username) 方法获取到的 userDetails 对象。因为返回给客户端的数据中不应再存有 password 信息，所以默认情况下，会对 principalToReturn —— 也就是 loadUserByUsername() 返回的 userDetails 实例调用 eraseCredentials() 方法，将 password 字段设为 null。所以，我们在实现 loadUserByUsername() 方法时，应注意不要直接返回保存的 userDetails 的引用，而是返回其数据副本。在 User 类的文档中也强调了 User 并不是不可变对象，而在 spring security 提供的 InMemoryUserDetailsManager 和 JdbcUserDetailsManager 中，其 loadUserByUsername() 方法也是返回了保存的实例的副本。</p>
<h2 id="自定义设置"><a href="#自定义设置" class="headerlink" title="自定义设置"></a>自定义设置</h2><p>通常，这一验证流程中我们需要自定义的部分有：</p>
<ul>
<li><p>filter，默认的实现 UsernamePasswordAuthenticationFilter 中使用 request.getParameter() 读取参数，这一方法的文档中说明，只能读取 GET 方法的 url 参数和 POST 方法使用 form-data 及 x-www-form-urlencoded 的参数，如果在 RequestBody 中传输 json 等格式的数据，则不能读取。所以需要我们自行实现 filter 来替换默认的 filter。</p>
</li>
<li><p>UserDetailsService，自定义服务端读取已保存的用户认证信息。我们可以实现 UserDetailsManager 接口管理用户信息缓存以及数据库存取，或者使用 JdbcUserDetailsManager 类在数据库中存取。</p>
</li>
<li><p>PasswordEncoder，设置使用的密码编码方式。</p>
</li>
</ul>
<p>以下进行示例。</p>
<ol>
<li></li>
</ol>
<p>妈的累死了，越写越大，考虑了下，光实现 UserDetailsManager 就可以再整合缓存等，全写完怕是累死了…我先缓缓，今天周六，放个假~</p>

    </div>

    
    
    

      <footer class="post-footer">
          <div class="post-tags">
              <a href="/tags/java/" rel="tag"># java</a>
              <a href="/tags/Spring-Boot/" rel="tag"># Spring Boot</a>
              <a href="/tags/Spring-security/" rel="tag"># Spring security</a>
          </div>

        


        
    <div class="post-nav">
      <div class="post-nav-item">
    <a href="/c8a6c539.html" rel="prev" title="zabbix的部署">
      <i class="fa fa-chevron-left"></i> zabbix的部署
    </a></div>
      <div class="post-nav-item">
    <a href="/f3d75b35.html" rel="next" title="自定义RedisTemplate初探">
      自定义RedisTemplate初探 <i class="fa fa-chevron-right"></i>
    </a></div>
    </div>
      </footer>
    
  </article>
  
  
  



          </div>
          

<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      let commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>

        </div>
          
  
  <div class="toggle sidebar-toggle">
    <span class="toggle-line toggle-line-first"></span>
    <span class="toggle-line toggle-line-middle"></span>
    <span class="toggle-line toggle-line-last"></span>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
          <div class="post-toc motion-element"><ol class="nav"><li class="nav-item nav-level-2"><a class="nav-link" href="#%E4%B8%BB%E8%A6%81%E7%9A%84%E7%B1%BB"><span class="nav-number">1.</span> <span class="nav-text">主要的类</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E8%AE%A4%E8%AF%81%E6%B5%81%E7%A8%8B"><span class="nav-number">2.</span> <span class="nav-text">认证流程</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E8%87%AA%E5%AE%9A%E4%B9%89%E8%AE%BE%E7%BD%AE"><span class="nav-number">3.</span> <span class="nav-text">自定义设置</span></a></li></ol></div>
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
  <p class="site-author-name" itemprop="name">马天</p>
  <div class="site-description" itemprop="description">程序猿</div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/archives/">
        
          <span class="site-state-item-count">41</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
            <a href="/categories/">
          
        <span class="site-state-item-count">17</span>
        <span class="site-state-item-name">分类</span></a>
      </div>
      <div class="site-state-item site-state-tags">
            <a href="/tags/">
          
        <span class="site-state-item-count">25</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>
  <div class="links-of-author motion-element">
      <span class="links-of-author-item">
        <a href="https://github.com/matian2014" title="GitHub → https:&#x2F;&#x2F;github.com&#x2F;matian2014" rel="noopener" target="_blank"><i class="fab fa-github fa-fw"></i>GitHub</a>
      </span>
      <span class="links-of-author-item">
        <a href="mailto:matian1103@qq.com" title="E-Mail → mailto:matian1103@qq.com" rel="noopener" target="_blank"><i class="fa fa-envelope fa-fw"></i>E-Mail</a>
      </span>
  </div>



      </div>

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer class="footer">
      <div class="footer-inner">
        

        
  <div class="beian"><a href="https://beian.miit.gov.cn/" rel="noopener" target="_blank">陕ICP备2023001563号 </a>
  </div>

<div class="copyright">
  
  &copy; 
  <span itemprop="copyrightYear">2025</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">马天</span>
</div>
  <div class="powered-by">由 <a href="https://hexo.io/" class="theme-link" rel="noopener" target="_blank">Hexo</a> & <a href="https://muse.theme-next.org/" class="theme-link" rel="noopener" target="_blank">NexT.Muse</a> 强力驱动
  </div>

        








      </div>
    </footer>
  </div>

  
  <script src="/lib/anime.min.js"></script>
  <script src="/lib/velocity/velocity.min.js"></script>
  <script src="/lib/velocity/velocity.ui.min.js"></script>

<script src="/js/utils.js"></script>

<script src="/js/motion.js"></script>


<script src="/js/schemes/muse.js"></script>


<script src="/js/next-boot.js"></script>




  















  

  

</body>
</html>
